package com.sevennotes.classtable.data.class_items.impl

import android.content.Context
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.map
import androidx.paging.PagingSource
import androidx.room.Room
import com.sevennotes.classtable.data.class_items.ClassesRespository
import com.sevennotes.classtable.data.database.ClassTableDatabase
import com.sevennotes.classtable.model.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.withContext
import java.text.Collator
import java.util.*

class ClassesRespositoryImpl(context: Context) : ClassesRespository {

  private val db =
    Room.databaseBuilder(context, ClassTableDatabase::class.java, "class_table")
      .allowMainThreadQueries()
      .build()

  override suspend fun getClassItems(tableId: Int): LiveData<List<ClassItemWithName>> {
    return db.classItemDao().getAllClassItemsWithName(tableId)
  }

  override suspend fun addClassItem(item: ClassItemWithName) {
    addClassItem(item.classItem.copy(id = null))
  }

  override suspend fun addClassItem(item: ClassItem) {
    db.classItemDao().insertClassItem(item.copy(id = null))
  }

  override suspend fun deleteClassItem(itemId: Int) {
    db.classItemDao().deleteClassItem(ClassItem(id = itemId))
  }

  override suspend fun updateClassItem(item: ClassItemWithName) {
    updateClassItem(item.classItem)
  }

  override suspend fun updateClassItem(item: ClassItem) {
    db.classItemDao().updateClassItem(item)
  }

  override suspend fun getAllStudents(): LiveData<List<StudentItem>> {
    val students = db.studentItemDao().getAllStudentItems()
    val c = Collator.getInstance(Locale.CHINA)
    return students.map { list ->
      list.sortedWith { t, t2 -> c.compare(t.name, t2.name) }
    }
  }

  override suspend fun addStudent(item: StudentInfo) {
    val studentId = db.studentItemDao().insertStudentItem(item.studentItem.copy(id = null)).toInt()
    item.classes.forEach {
      db.classItemDao().insertClassItem(it.copy(id = null, studentId = studentId))
    }
    db.gradePointRecordDao().getAllById(-1).forEach {
      db.gradePointRecordDao().updateGradePointRecord(it.copy(studentId = studentId))
    }
    db.classAmountRecordDao().getAllById(-1).forEach {
      db.classAmountRecordDao().updateClassAmount(it.copy(studentId = studentId))
    }
  }

  override suspend fun updateStudent(item: StudentInfo) {
    if (item.studentItem.id == -1) {
      addStudent(item)
      return
    }
    db.studentItemDao().updateStudentItem(item.studentItem)
    item.classes.forEach {
      if (it.id == -1) addClassItem(it) else updateClassItem(it)
    }
  }

  override suspend fun updateStudentGradePoint(studentItem: StudentItem) {
    db.studentItemDao().updateStudentItem(studentItem)
  }

  override suspend fun deleteStudent(studentItem: StudentItem) {
    db.studentItemDao().deleteStudentItem(studentItem)
    studentItem.id?.let {
      db.classItemDao().deleteStuAllClasses(it)
    }
  }

  override suspend fun getStudentInfoById(studentId: Int, tableId: Int): StudentInfo? {
    val student = db.studentItemDao().getStudentItemById(studentId)
    val classList = db.classItemDao().getClassItemByStudentId(studentId, tableId)
    if (student == null) return null
    return StudentInfo(student, classList)
  }

  override suspend fun addTableName(tableName: TableName): Long {
    return db.tablenamesDao().createNewTable(tableName)
  }

  override suspend fun deleteTableName(tableName: TableName) {
    db.tablenamesDao().deleteTableName(tableName)
  }

  override suspend fun updateTableName(tableName: TableName) {
    db.tablenamesDao().updateTableName(tableName)
  }

  override suspend fun getAllTableNames(): LiveData<List<TableName>> {
    return db.tablenamesDao().getAllTables()
  }

  override suspend fun getFirstTableName(): TableName {
    return db.tablenamesDao().getFirstTable()
  }

  override suspend fun getTableCount(): Int {
    return db.tablenamesDao().getTableCount()
  }

  override suspend fun copyClassesToNewTable(oldTableId: Int, newTableId: Int) {
    val oldClasses = db.classItemDao().getAllClassItemsByTableId(oldTableId)
    oldClasses.forEach {
      db.classItemDao().insertClassItem(it.copy(id = null, tableId = newTableId))
    }
  }

  override suspend fun insertClassRecord(classRecord: ClassRecord) {
    db.studentItemDao().getStudentItemById(classRecord.studentId)?.let {
      db.studentItemDao().updateStudentItem(
        it.copy(
          gradePoint = it.gradePoint + classRecord.gpChange,
          classAmount = it.classAmount - 1
        )
      )
    }
    db.classRecordDao().insertClassRecord(classRecord)
  }

  override suspend fun insertClassAmountRecord(classAmountRecord: ClassAmountRecord) {
    if (classAmountRecord.studentId != -1) {
      db.studentItemDao().getStudentItemById(classAmountRecord.studentId)?.let {
        db.studentItemDao()
          .updateStudentItem(it.copy(classAmount = it.classAmount + classAmountRecord.amountChange))
      }
    }
    db.classAmountRecordDao().insertClassAmount(classAmountRecord)
  }

  override suspend fun insertGradePointRecord(gradePointRecord: GradePointRecord) {
    if (gradePointRecord.studentId != -1) {
      db.studentItemDao().getStudentItemById(gradePointRecord.studentId)?.let {
        db.studentItemDao()
          .updateStudentItem(it.copy(gradePoint = it.gradePoint + gradePointRecord.gpChange))
      }
    }
    db.gradePointRecordDao().insertGradePointRecord(gradePointRecord)
  }

  override suspend fun deleteClassAmountRecord(classAmountRecord: ClassAmountRecord) {
    if (classAmountRecord.studentId != -1) {
      db.studentItemDao().getStudentItemById(classAmountRecord.studentId)?.let {
        db.studentItemDao()
          .updateStudentItem(it.copy(classAmount = it.classAmount - classAmountRecord.amountChange))
      }
    }
    db.classAmountRecordDao().deleteClassAmount(classAmountRecord)
  }

  override suspend fun deleteGradePointRecord(gradePointRecord: GradePointRecord) {
    if (gradePointRecord.studentId != -1) {
      db.studentItemDao().getStudentItemById(gradePointRecord.studentId)?.let {
        db.studentItemDao()
          .updateStudentItem(it.copy(gradePoint = it.gradePoint - gradePointRecord.gpChange))
      }
    }
    db.gradePointRecordDao().deleteGradePointRecord(gradePointRecord)
  }

  override suspend fun getClassAmountRecordById(studentId: Int): LiveData<List<ClassAmountRecord>> {
    return db.classAmountRecordDao().getAllClassAmountByStudentId(studentId)
  }

  override suspend fun clearUnexistRecord() {
    db.gradePointRecordDao().deleteAllByStudentId(-1)
    db.classAmountRecordDao().deleteAllByStudentId(-1)
  }

  override suspend fun getGpRecordById(studentId: Int): LiveData<List<GradePointRecord>> {
    return db.gradePointRecordDao().getAllGradePointRecordByStudentId(studentId)
  }

  override fun getAllClassRecordOld(
    startTime: Date,
    toTime: Date,
    studentName: String
  ): PagingSource<Int, ClassRecordWithName> {
    val a = db.classRecordDao().getAllClassRecordOld(startTime.time, toTime.time, studentName)
    Log.d("test", "$a")
    return a
  }

  override fun getAllClassRecord(): PagingSource<Int, ClassRecordWithName> {
    return db.classRecordDao().getAllClassRecord()
  }

  override suspend fun getAllClassRecordsByStudentId(studentId: Int): List<ClassRecord>
  = withContext(Dispatchers.IO) {
    db.classRecordDao().getAllClassRecordsByStudentId(studentId)
  }

  override fun getClassRecordMonthStatistics(): Flow<List<ClassRecordMonthStatis>> {
    return db.classRecordDao().getClassRecordMonthStatistics()
  }

  override fun getAllStartYears(): List<String> {
    return db.classRecordDao().getAllStartYears()
  }

  override fun getClassRecordByYears(yearList: List<String>): List<ClassRecordWithName> {
    return db.classRecordDao().getClassRecordsByYears(yearList)
  }

  override suspend fun getClassRecordCount(
    startTime: Date,
    toTime: Date,
    studentName: String
  ): Int {
    return db.classRecordDao().getCount(startTime.time, toTime.time, studentName)
  }

  override suspend fun deleteClassRecord(classRecord: ClassRecord) {
    db.studentItemDao().getStudentItemById(classRecord.studentId).let {
      if (it != null) {
        db.studentItemDao().updateStudentItem(
          it.copy(
            gradePoint = it.gradePoint - classRecord.gpChange,
            classAmount = it.classAmount + 1
          )
        )
      }
    }
    db.classRecordDao().deleteClassRecord(classRecord)
  }

  override suspend fun getBackupItem(): ClassTableBackup {
    val backup = ClassTableBackup().also {
      it.classAmountRecord = db.classAmountRecordDao().getAllCARForBack()
      it.classItem = db.classItemDao().getAllClassItems()
      it.classRecord = db.classRecordDao().getAllClassRecordForBack()
      it.gradePointRecord = db.gradePointRecordDao().getAllGPRForBack()
      it.studentItem = db.studentItemDao().getAllStudentItemsForBack()
      it.tableName = db.tablenamesDao().getAllTablesForBack()
    }
    return backup
  }

  override suspend fun clearAllDBs() {
    db.classAmountRecordDao().clear()
    db.classItemDao().clear()
    db.classRecordDao().clear()
    db.gradePointRecordDao().clear()
    db.studentItemDao().clear()
    db.tablenamesDao().clear()
  }

  override suspend fun recovery(backup: ClassTableBackup) {
    backup.classAmountRecord?.let {
      it.forEach { ca ->
        db.classAmountRecordDao().insertClassAmount(ca)
      }
    }
    backup.classItem?.let {
      it.forEach { ci ->
        db.classItemDao().insertClassItem(ci)
      }
    }
    backup.classRecord?.let {
      it.forEach { cr ->
        db.classRecordDao().insertClassRecord(cr)
      }
    }
    backup.gradePointRecord?.let {
      it.forEach { gp ->
        db.gradePointRecordDao().insertGradePointRecord(gp)
      }
    }
    backup.studentItem?.let {
      it.forEach { si ->
        db.studentItemDao().insertStudentItem(si)
      }
    }
    backup.tableName?.let {
      it.forEach { tn ->
        db.tablenamesDao().createNewTable(tn)
      }
    }
  }
}